{%mainunit bitcoin_p2p.pas}

{$IFDEF INTERFACE}

{ Read from the socket and not return until all bytes have been read }
function ReadBlocking(ASocket: TLSocket; var Target; Size: SizeInt): Boolean;

{ After this the next readable byte will be the first byte of the message.
  This procedue tries to solve all the ugliness related to different
  header sizes, etc.}
function ReadHeaderFromSocket(ASocket: TLSocket; out Header: TP2PHeader): Boolean;

{$ENDIF}
{$IFDEF IMPLEMENTATION}

function ReadBlocking(ASocket: TLSocket; var Target; Size: SizeInt): Boolean;
var
  Position:  SizeInt = 0;
  BytesRead: SizeInt;
begin
  repeat
    BytesRead := ASocket.Get(PByte(@Target)[Position], Size-Position);
    if BytesRead = 0 then exit(False);
    Inc(Position, BytesRead);
  until Position = Size;
  Result := True;
end;

function ReadHeaderFromSocket(ASocket: TLSocket; out Header: TP2PHeader): Boolean;
var
  Count : Integer = 0;
begin
  with {%H-}Header do begin
    DWord(Magic) := 0;
    repeat
      // We are reading single bytes until we find the magic sequence. To make
      // it easier and avoid another endian ifdef we treat it as a byte array.
      Magic[0] := Magic[1]; // I could have used a shift operation to shift an
      Magic[1] := Magic[2]; // entire DWord and save few cycles but I would have
      Magic[2] := Magic[3]; // needed 3 lines room to write a comment anyways ;-)
      if ReadBlocking(ASocket, Magic[3], 1) = False then exit(False);
      Inc(Count);
    until DWord(Magic) = DWord(MAGIC_BYTES);

    if Count > 4 then begin
      // this should not happen (but also not break it),
      // we log this strange event and continue.
      log('*** magic was out of sync, jumped over %d bytes', [Count-4]);
    end;

    // Now read Command (12 bytes) + Length (4 bytes)
    if ReadBlocking(ASocket, Command, 16) = False then exit(False);

    // at this point we might have to stop reading if it is a short header
    // or there might be 4 bytes more to read if it is a normal header.
    if (PChar(@Command) = 'version') or (PChar(@Command) = 'verack') then begin
      DWord(CheckSum) := 0;
    end else begin
      // we consequently treat the checksums always as 4 byte string
      // and never as Integer number to avoid endianness confusion.
      // We just read it into the buffer as it comes first byte first.
      if ReadBlocking(ASocket, CheckSum, 4) = False then exit(False);
    end;
    {$ifdef ENDIAN_BIG}
    Length := Swap(Length);
    {$endif}
  end;
  Result := True;
end;

{$ENDIF}
